package it.iaproject;

import it.iaproject.ai.MarioIA;
import it.iaproject.ui.Ui;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;
import java.util.Vector;


public class Map {

	public static final int VARIATIONCOEF = 4;
	public static final int DELTAOFTHERIVERCOEF = 4;
	
	public static final int NUMOFENEMYCOEF = 4;
	
	// Piu' il valore e' alto minore sara' il numero di ponti
	public static final int NUMOFBRIEDGSCOEF = 5;
	

	private int dim;
	private Cell[][] matrix;
	private Vector<Cell> riversCell;
	private Vector<Cell> briedgs;
	private Cell marioCell;
	private Cell peachCell;

	private Cell[][] originaryMap;
	private Cell originaryMarioCell;

	public MarioIA ia;

	public Ui ui;

	/**
	 * Creo la mappa: <br>
	 * Inizializzo la mappa vuota <br>
	 * Creo il fiume <br>
	 * Creo i ponti <br>
	 * Creo i nemici <br>
	 * 
	 * @param dim Dimensione della mappa 
	 */
	public Map(int dim) {
		this.dim = dim;
		ui = new Ui(this);
		generateNewMap();
	}

	public void generateNewMap() {
		matrix = new Cell[dim][dim];
		riversCell = new Vector<Cell>();

		for(int i = 0; i < dim; i++) {
			for(int j = 0; j < dim; j++) {
				matrix[i][j] = new Cell(i,j);
			}
		}

		System.out.println("");
		createRiver();

		while(!checkTheRiver()) {
			deleteRiver();
			riversCell = new Vector<Cell>();
			System.out.println("create the river again!!!!");
			createRiver();
		}
		
		System.out.println("River Creato");
		createBridges();
		System.out.println("Ponti creati");
		createEnemies();
		System.out.println("Nemici creati");
		placeMarioAndPeach();
		System.out.println("M e P piazzati");

		originaryMap = new Cell[dim][dim];
		for (int i = 0; i<dim; i++) {
			for (int j = 0; j<dim; j++) {
				originaryMap[i][j] = new Cell(i, j);
				originaryMap[i][j].setStatus(matrix[i][j].getStatus());
			}
		}
		
		originaryMarioCell = originaryMap[marioCell.getRow()][marioCell.getCol()];

		if(ia != null) {
			synchronized (ia) {
				ia.notify();
				ia.setStop(true);				
			}
		}
		ia = new MarioIA(marioCell, this);
		ui.updateMapPanel();
		ia.start();	
		
		System.out.println("===================================");
		System.out.println("============ MARIO GO!! ===========");
		System.out.println("===================================");
	}

	private void deleteRiver() {
		for(int i=0; i<riversCell.size();i++) {
			riversCell.elementAt(i).setStatus(Cell.FREE);
		}

	}

	@SuppressWarnings("unused")
	private void printRiver() {
		for(int i = 0; i < riversCell.size(); i++) {
			System.out.println("["+riversCell.elementAt(i).getRow()+"]["+riversCell.elementAt(i).getCol()+"]");
		}
	}

	/**
	 * Controlla che il numero di celle del fiume lungo i lati sia inferiore alla soglia indicata
	 * @return risultao del controllo
	 */
	private boolean checkTheRiver() {
		int onSideCounter = 0;
		for(int i = 0; i < riversCell.size() && !(onSideCounter > 2); i++) {
			if(riversCell.elementAt(i).getCol() == 0 ||
					riversCell.elementAt(i).getCol() == dim-1 ||
					riversCell.elementAt(i).getRow() == 0 ||
					riversCell.elementAt(i).getRow() == dim-1) {
				onSideCounter++;
			}
		}
		if(onSideCounter > 2)
			return false;
		else 
			return true;
	}

	private void placeMarioAndPeach() {

		Random randomGenerator = new Random(System.currentTimeMillis());
		int riverCellMario = randomGenerator.nextInt(riversCell.size());
		int riverCellPeach = randomGenerator.nextInt(riversCell.size());
		boolean placedPeach = false;
		boolean placedMario = false;

		if(riversCell.elementAt(0).getCol() == 0) {
			// col == 0 => sx to dx
			int fixedCol;

			fixedCol = riversCell.elementAt(riverCellPeach).getCol();
			int counter = 0;
			while(!placedPeach) {
				if(counter == dim) {
					// Ho effettuato troppi tentativi, probabilmente non c'e' una posizione utile, meglio cambiare posizione
					riverCellPeach = randomGenerator.nextInt(riversCell.size());
					fixedCol = riversCell.elementAt(riverCellPeach).getCol();
					counter = 0;
				}
				// tengo fissa la riga e mi muovo sulle colonne verso sx
				int randomRow = randomGenerator.nextInt(riversCell.elementAt(riverCellPeach).getRow());
				if(matrix[randomRow][fixedCol].getStatus() == Cell.FREE) {
					matrix[randomRow][fixedCol].setStatus(Cell.PEACH);
					setPeachCell(matrix[randomRow][fixedCol]);
					placedPeach = true;
				}
				counter++;
			}

			fixedCol = riversCell.elementAt(riverCellMario).getCol();
			counter = 0;
			while(!placedMario) {
				if(counter == dim) {
					// Ho effettuato troppi tentativi, probabilmente non c'e' una posizione utile, meglio cambiare posizione
					riverCellMario = randomGenerator.nextInt(riversCell.size());
					fixedCol = riversCell.elementAt(riverCellMario).getCol();
					counter = 0;
				}
				int randomRow = randomGenerator.nextInt(dim - riversCell.elementAt(riverCellMario).getRow());
				// tengo fissa la riga e mi muovo sulle colonne verso sx
				if(matrix[randomRow + riversCell.elementAt(riverCellMario).getRow()][fixedCol].getStatus() == Cell.FREE) {
					matrix[randomRow + riversCell.elementAt(riverCellMario).getRow()][fixedCol].setStatus(Cell.MARIO);
					setMarioCell(matrix[randomRow + riversCell.elementAt(riverCellMario).getRow()][fixedCol]);
					placedMario = true;
				}
				counter++;
			}
		} else {
			// col != 0 => row == 0 => up to down
			int fixedRow;
			fixedRow = riversCell.elementAt(riverCellPeach).getRow();
			int counter = 0;
			while(!placedPeach) {
				if(counter == dim) {
					// Ho effettuato troppi tentativi, probabilmente non c'e' una posizione utile, meglio cambiare posizione
					riverCellPeach = randomGenerator.nextInt(riversCell.size());
					fixedRow = riversCell.elementAt(riverCellPeach).getRow();
					counter = 0;
				}
				int randomCol = randomGenerator.nextInt(riversCell.elementAt(riverCellPeach).getCol());
				// tengo fissa la riga e mi muovo sulle colonne verso sx
				if(matrix[fixedRow][randomCol].getStatus() == Cell.FREE) {
					matrix[fixedRow][randomCol].setStatus(Cell.PEACH);
					setPeachCell(matrix[fixedRow][randomCol]);
					placedPeach = true;
				}
				counter++;
			}
			fixedRow = riversCell.elementAt(riverCellMario).getRow();
			counter = 0;
			while(!placedMario) {
				if(counter == dim) {
					// Ho effettuato troppi tentativi, probabilmente non c'e' una posizione utile, meglio cambiare posizione
					riverCellMario = randomGenerator.nextInt(riversCell.size());
					fixedRow = riversCell.elementAt(riverCellMario).getRow();
					counter = 0;
				}
				int randomCol = randomGenerator.nextInt(dim - riversCell.elementAt(riverCellMario).getCol());
				// tengo fissa la riga e mi muovo sulle colonne verso sx
				if(matrix[fixedRow][riversCell.elementAt(riverCellMario).getCol() + randomCol].getStatus() == Cell.FREE) {
					matrix[fixedRow][riversCell.elementAt(riverCellMario).getCol() + randomCol].setStatus(Cell.MARIO);
					setMarioCell(matrix[fixedRow][riversCell.elementAt(riverCellMario).getCol() + randomCol]);
					placedMario = true;
				}
				counter++;
			}
		}
	}

	private void createEnemies() {
		Random randomGenerator = new Random(System.currentTimeMillis());

		int randomRow;
		int randomCol;

		int numOfEnemies = (dim / NUMOFENEMYCOEF);

		Boolean done = false;
		Vector<Cell> neighbours;
		
		int attempts;
		
		for(int i = 0; i < numOfEnemies; i++) {
			for(int j = 0; j < numOfEnemies; j++) {
				attempts = numOfEnemies*numOfEnemies;
				done = false;
				while(!done) {
					randomRow = randomGenerator.nextInt(NUMOFENEMYCOEF) + (NUMOFENEMYCOEF * i);
					randomCol = randomGenerator.nextInt(NUMOFENEMYCOEF) + (NUMOFENEMYCOEF * j);
					if(matrix[randomRow][randomCol].getStatus() == Cell.FREE) {
						neighbours = neighbour8Cells(matrix[randomRow][randomCol]);
						Boolean fail = false;
						for(int z = 0; z < neighbours.size() && !fail; z++) {
							if(neighbours.elementAt(z).getStatus() == Cell.RIVER) {
								fail = true;
								attempts--;
							}
						}
						if(!fail) {
							matrix[randomRow][randomCol].setStatus(Cell.ENEMY);
							done = true;
						} else if(attempts == 0) {
							//Non riesco a matter giu' il mostro
							done = true;
						}
					}
				}
			}
		}
	}

	/**
	 * Lungo il fiume creiamo dei ponti secondo la lunghezza del fiume e del coef specificato
	 */
	private void createBridges() {
		briedgs = new Vector<Cell>();
		Random randomGenerator = new Random(System.currentTimeMillis());
		int numOfBriedgs = riversCell.size() / NUMOFBRIEDGSCOEF;
		
		int numRandom;
		while(numOfBriedgs > 0) {
			numRandom = randomGenerator.nextInt(riversCell.size());
			Boolean ok = true;
			if(!(riversCell.elementAt(numRandom).getStatus() == Cell.RIVER)) {
				ok = false;
			}
			if(numRandom-1 >= 0)
				if(riversCell.elementAt(numRandom-1).getStatus() == Cell.BRIDGE) {
					ok = false;
				}
			if(numRandom+1 < riversCell.size())
				if(riversCell.elementAt(numRandom+1).getStatus() == Cell.BRIDGE) {
					ok = false;
				}
			if((riversCell.elementAt(numRandom).getCol() != 0) &&
					(riversCell.elementAt(numRandom).getCol() != dim-1) &&
					(riversCell.elementAt(numRandom).getRow() != 0) &&
					(riversCell.elementAt(numRandom).getRow() != dim-1)) {
				if(!((matrix[riversCell.elementAt(numRandom).getRow()][riversCell.elementAt(numRandom).getCol()-1].getStatus() == Cell.RIVER &&
					matrix[riversCell.elementAt(numRandom).getRow()][riversCell.elementAt(numRandom).getCol()+1].getStatus() == Cell.RIVER)
					|| 
					(matrix[riversCell.elementAt(numRandom).getRow()-1][riversCell.elementAt(numRandom).getCol()].getStatus() == Cell.RIVER &&
					matrix[riversCell.elementAt(numRandom).getRow()+1][riversCell.elementAt(numRandom).getCol()].getStatus() == Cell.RIVER))){
					ok = false;
				}
			}
			if(ok) {
				riversCell.elementAt(numRandom).setStatus(Cell.BRIDGE);
				briedgs.add(riversCell.elementAt(numRandom));
				numOfBriedgs--;
			}

		}
	}


	/**
	 * Codice incomprensibile, basti sapere che:<br>
	 * Vogliamo che il fiume attraversi la mappa ma che possa deviare il suo corso: <br>
	 * nel range di un numero di celle proporzionale ai paramentri specificati, <br>
	 * un numero massimo di volte sempre secondo i paramentri specificati, <br>
	 * lasciando una certa quantita' di spazio nelle due porzioni di mappa creati da fiume (sempre paramentrica),<br>
	 * che non possa deviare due volte a fila
	 */
	private void createRiver() {
		Random randomGenerator = new Random(System.currentTimeMillis());

		int riverStartCell = randomGenerator.nextInt(dim - 4) + 2;
		int river = riverStartCell;
		int variation = dim/VARIATIONCOEF;
		int deltaOfTheRiver = dim/DELTAOFTHERIVERCOEF;

		int doneLastTime = 0;
		int justDone = 0;

		// > 5 fiume che taglia a meta' la mappa dall'alto verso il basso
		// < 5 fiume da sinistra a destra
		if(randomGenerator.nextInt(10) < 5) { //
			for(int i=0; i<dim; i++) {
				if(justDone > 0)
					justDone--;
				matrix[river][i].setStatus(Cell.RIVER);
				riversCell.add(matrix[river][i]);
				if(variation > 0 && doneLastTime == 0 && i > 0 && i < dim - 1) {
					if(randomGenerator.nextInt(10) < 5) {
						if(randomGenerator.nextInt(10) < 5) {
							if(river - riverStartCell < deltaOfTheRiver && inRange(river)) {
								river++;
								doneLastTime = 3;
								justDone = 3;
								matrix[river][i].setStatus(Cell.RIVER);
								riversCell.add(matrix[river][i]);
								variation--;
								while(randomGenerator.nextInt(10) < 5 && river - riverStartCell < deltaOfTheRiver && inRange(river)) {
									river++;
									matrix[river][i].setStatus(Cell.RIVER);
									riversCell.add(matrix[river][i]);
								}
							}
						} else {
							if(riverStartCell - river < deltaOfTheRiver && inRange(river)) {
								river--;
								doneLastTime = 3;
								justDone = 3;
								matrix[river][i].setStatus(Cell.RIVER);
								riversCell.add(matrix[river][i]);
								variation--;
								while(randomGenerator.nextInt(10) < 5 && riverStartCell - river < deltaOfTheRiver && inRange(river)) {
									river--;
									matrix[river][i].setStatus(Cell.RIVER);
									riversCell.add(matrix[river][i]);
								}
							}
						}
					}
				}
				if(justDone > 0) {
					doneLastTime--;
				}
			}
		} else {
			for(int i=0; i<dim; i++) {
				if(justDone > 0)
					justDone--;
				matrix[i][river].setStatus(Cell.RIVER);
				riversCell.add(matrix[i][river]);
				if(variation > 0 && doneLastTime == 0 && i > 0 && i < dim - 1) {
					if(randomGenerator.nextInt(10) < 5) {
						if(randomGenerator.nextInt(10) < 5) {
							if(river - riverStartCell < deltaOfTheRiver && inRange(river)) {
								river++;
								doneLastTime = 3;
								justDone = 3;
								matrix[i][river].setStatus(Cell.RIVER);
								riversCell.add(matrix[i][river]);
								variation--;
								while(randomGenerator.nextInt(10) < 5 && river - riverStartCell < deltaOfTheRiver && inRange(river)) {
									river++;
									matrix[i][river].setStatus(Cell.RIVER);
									riversCell.add(matrix[i][river]);
								}
							}
						} else {
							if(riverStartCell - river < deltaOfTheRiver && inRange(river)) {
								river--;
								doneLastTime = 3;
								justDone = 3;
								matrix[i][river].setStatus(Cell.RIVER);
								riversCell.add(matrix[i][river]);
								variation--;
								while(randomGenerator.nextInt(10) < 5 && riverStartCell - river < deltaOfTheRiver && inRange(river)) {
									river--;
									matrix[i][river].setStatus(Cell.RIVER);
									riversCell.add(matrix[i][river]);
								}
							}
						}
					}
				}
				if(justDone > 0) {
					doneLastTime--;
				}
			}
		}
	}

	public Vector<Cell> neighbour8Cells(Cell myCell) {
		//System.out.println("------------------["+myCell.getRow()+"]["+myCell.getCol()+"]------------------");
		int row = myCell.getRow();
		int col = myCell.getCol();
		Vector<Cell> neighbours = new Vector<Cell>();
		int initI = Math.max(0, row - 1);
		int initJ = Math.max(0, col - 1);
		for (int i = initI; i < initI+Math.min(3+(row-1<0?row-1:0), dim - row + 1); i++) {
			for(int j = initJ; j < initJ+Math.min(3+(col-1<0?col-1:0), dim - col + 1); j++) {
				//System.out.print("["+i+"]["+j+"] ");
				if(!(i == row && j == col))
					neighbours.add(matrix[i][j]);
			}
			//System.out.println();
		}
		//System.out.println("------------------------------------");
		return neighbours;
	}

	public Vector<Cell> neighbour4Cells(Cell myCell) { 
		int row = myCell.getRow();
		int col = myCell.getCol();
		Vector<Cell> neighbours = new Vector<Cell>();
		if((row - 1) > 0)
			neighbours.add(matrix[row-1][col]);
		if((row + 1) < dim)
			neighbours.add(matrix[row+1][col]);
		if((col - 1) > 0)
			neighbours.add(matrix[row][col - 1]);
		if((col + 1) < dim)
			neighbours.add(matrix[row][col + 1]);
		
		return neighbours;
	}


	private Boolean inRange(int toCheck) {
		return (toCheck < dim - 1 && toCheck > 1);
	}

	public Cell getCell(int x, int y) {
		return matrix[x][y];
	}

	public void printMap() {
		for(int i = 0; i < dim; i++) {
			for(int j = 0; j < dim; j++) {
				System.out.print(matrix[i][j].getStatus() + " ");
			}
			System.out.println("");
		}
	}

	public int getDim() { return dim; }
	public void setDim(int dim) { this.dim = dim; }

	public Cell getMarioCell() {
		return marioCell;
	}

	public void setMarioCell(Cell marioCell) {
		this.marioCell = marioCell;
	}

	public Cell getPeachCell() {
		return peachCell;
	}

	public void setPeachCell(Cell peachCell) {
		this.peachCell = peachCell;
	}

	public Cell getOriginaryMarioCell() {
		return originaryMarioCell;
	}

	public void setOriginaryMarioCell(Cell originaryMarioCell) {
		this.originaryMarioCell = originaryMarioCell;
	}

	public void restoreOriginalMap() {
		matrix = new Cell[dim][dim];
		for (int i = 0; i<dim; i++) {
			for (int j = 0; j<dim; j++) {
				matrix[i][j] = new Cell(i, j);
				matrix[i][j].setStatus(originaryMap[i][j].getStatus());
			}
		}

		marioCell = matrix[originaryMarioCell.getRow()][originaryMarioCell.getCol()];
		ui.updateMapPanel();
	}

	public void saveOnCSV(int heuristic, int numOfNodes) {
		try {
			
//			File csvFile;
//			if (new File("restult.csv").exists()) {
//				csvFile = new File("restult.csv");
//			} else {
//				int i = 1;
//				while (new File("restult" + i + ".csv").exists()) {
//					i++;
//				}
//				csvFile = new File("restult" + i + ".csv");
//			}

			
			BufferedWriter csvFile = new BufferedWriter(new FileWriter(new File("restult.csv"), true));
			
			csvFile.write(dim + ",");
			csvFile.write(marioCell.getRow() + ",");
			csvFile.write(marioCell.getCol() + ",");
			csvFile.write(peachCell.getRow() + ",");
			csvFile.write(peachCell.getCol() + ",");
			csvFile.write(heuristic + ",");
			csvFile.write(new Integer(numOfNodes).toString());
			
			csvFile.write("\n");
			csvFile.flush();
			csvFile.close();
			
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}